#include "kpextMemoryResource.h"

using namespace kpext;

RawBuffer::RawBuffer(std::shared_ptr<vk::PhysicalDevice> phydevice,
               std::shared_ptr<vk::Device> device
               ):
    m_expectBytesSize(4),
    m_usage(),
    m_sharing(vk::SharingMode::eExclusive),
    m_buf(nullptr),
    m_bufview(nullptr),
    m_format()
{
    m_phyDevice = phydevice;
    m_device = device;
    if(!m_device || !m_phyDevice){
        throw std::runtime_error(__FUNCTION__ " input with null pointer!");
    }

    m_descBinding = 0;  // UINT_MAX std::numeric_limits<uint32_t>::max();
    m_descBindingArrayLen = 1;
    {
        using UsageE = vk::BufferUsageFlagBits;
        using DescT = vk::DescriptorType;
        using ShaderSFlag = vk::ShaderStageFlagBits;
        if(m_usage & UsageE::eStorageBuffer){
            m_descType = DescT::eStorageBuffer;
        }else if(m_usage & UsageE::eUniformTexelBuffer){
            m_descType = DescT::eUniformTexelBuffer;
        }else if(m_usage & UsageE::eUniformBuffer){
            m_descType = DescT::eUniformBuffer;
        }else if(m_usage & UsageE::eStorageTexelBuffer){
            m_descType = DescT::eStorageTexelBuffer;
        }else{
            m_descType = DescT::eStorageBuffer;
        }
        m_descStage = vk::ShaderStageFlags(ShaderSFlag::eCompute);
    }

}

RawBuffer::~RawBuffer()
{
    if(m_bufview){
        m_device->destroyBufferView(*m_bufview);
        m_bufview = nullptr;
    }

    if(m_buf){
        m_device->destroyBuffer(*m_buf);
        m_buf = nullptr;
    }
}

bool RawBuffer::recreate(size_t bytesSize,
                      vk::BufferUsageFlags usage,
                      vk::SharingMode sharing)
{
    if(bytesSize < 1){
        SPDLOG_ERROR(__FUNCTION__ " input with zero size!");
        bytesSize = 1;
    }

    if(sharing != vk::SharingMode::eConcurrent
            && sharing != vk::SharingMode::eExclusive ){
        sharing = m_sharing;
    }
    if(sharing == vk::SharingMode::eConcurrent
            || sharing == vk::SharingMode::eExclusive ){
        sharing = vk::SharingMode::eExclusive;
    }
    vk::BufferCreateInfo bufferInfo(vk::BufferCreateFlags(),
                                    bytesSize,
                                    usage,
                                    sharing);

    auto buffer = std::make_shared<vk::Buffer>();
    auto result = m_device->createBuffer(&bufferInfo, nullptr, buffer.get());
    if(result != vk::Result::eSuccess){
        SPDLOG_ERROR("recreate vulkan buffer failed!");
        return false;
    }

    if(m_buf){
        m_device->destroyBuffer(*m_buf);
        m_buf = nullptr;
    }


    m_buf = buffer;

    m_expectBytesSize = bytesSize;
    m_usage = usage;
    if(sharing == vk::SharingMode::eConcurrent
            || sharing == vk::SharingMode::eExclusive ){
        m_sharing = sharing;
    }

    {
        using UsageE = vk::BufferUsageFlagBits;
        using DescT = vk::DescriptorType;
        if(m_usage & UsageE::eStorageBuffer){
            m_descType = DescT::eStorageBuffer;
        }else if(m_usage & UsageE::eUniformTexelBuffer){
            m_descType = DescT::eUniformTexelBuffer;
        }else if(m_usage & UsageE::eUniformBuffer){
            m_descType = DescT::eUniformBuffer;
        }else if(m_usage & UsageE::eStorageTexelBuffer){
            m_descType = DescT::eStorageTexelBuffer;
        }else{
            m_descType = DescT::eStorageBuffer;
        }
    }

    return true;
}

vk::Buffer *RawBuffer::getVKBuf()
{
    return m_buf.get();
}

vk::MemoryRequirements RawBuffer::getMemRequirements()
{
    if(m_device && m_buf){
        return m_device->getBufferMemoryRequirements(*m_buf);
    }else{
        return vk::MemoryRequirements();
    }
}

bool RawBuffer::bindMemory(RawDeviceMemory &memory, size_t offset)
{
    auto memptr = memory.getVKDevMem();
    if(m_device && m_buf && memptr){
        m_device->bindBufferMemory(
                    *m_buf,
                    *memptr,
                    offset
                    );
        return true;
    }else{
        return false;
    }
}

bool RawBuffer::recreateBufView(vk::Format format, size_t offset, size_t end)
{
    if(!m_buf){
        return false;
    }

    auto formatprops = m_phyDevice->getFormatProperties(format);
    const char fstr[] = "vulkan buffer with format={}, not support {}";
    const char errstr[] = "vulkan buffer format properties not satisfy!";
    bool cancreate = false;
    bool texelBufOk = false,
            uniformTexelBufOk = false,
            texelBufAtomicOk = false;
    kpext::checkTexelBufFormat(formatprops,
                               &texelBufOk,
                               &uniformTexelBufOk,
                               &texelBufAtomicOk
                               );

    if(m_usage & vk::BufferUsageFlagBits::eStorageTexelBuffer){
        if(!texelBufOk){
            SPDLOG_ERROR(fstr, vkFormat2Str(format), "StorageTexelBuffer");
            throw std::runtime_error(errstr);
        }
        cancreate = true;
    }

    if(m_usage & vk::BufferUsageFlagBits::eUniformTexelBuffer){
        if(!uniformTexelBufOk){
            SPDLOG_ERROR(fstr, vkFormat2Str(format), "UniformTexelBuffer");
            throw std::runtime_error(errstr);
        }
        cancreate = true;
    }

    if(!cancreate){
        return false;
    }

    if(!texelBufAtomicOk){
        SPDLOG_INFO(fstr, vkFormat2Str(format), "StorageTexelBufferAtomic");
    }

    size_t size;// = m_expectBytesSize;
    if(offset >= m_expectBytesSize){
        offset = m_expectBytesSize - 1;
    }

    if(end == 0){
        size = m_expectBytesSize - offset;
    }else if(end > offset){
        size = end - offset;
    }else{
        size = 1;
    }

    if(size > m_expectBytesSize - offset){
        size = m_expectBytesSize - offset;
    }

    vk::BufferViewCreateInfo bufferViewCreateInfo(
                {},
                *m_buf,
                format,
                offset,
                size);

    auto bufview = std::make_shared<vk::BufferView>();
    auto result = m_device->createBufferView(
                &bufferViewCreateInfo,
                nullptr,
                bufview.get()
                );
    if(result != vk::Result::eSuccess){
        return false;
    }

    if(m_bufview){
        m_device->destroyBufferView(*m_bufview);
        m_bufview = nullptr;
    }
    m_bufview = bufview;
    return true;
}

bool RawBuffer::recordCopy(const vk::CommandBuffer &commandBuffer,
                           const RawBuffer &dstBuf,
                           const RawBuffer &srcBuf,
                           size_t size,
                           size_t dstOffset,
                           size_t srcOffset)
{
    if(!dstBuf.m_buf || !srcBuf.m_buf){
        SPDLOG_DEBUG(__FUNCTION__ " input invalid kpext::RawBuffer");
        return false;
    }

    if(size == std::numeric_limits<size_t>::max()){
        size = dstBuf.m_expectBytesSize;
    }

    vk::BufferCopy copyRegion(srcOffset, dstOffset, size);
    commandBuffer.copyBuffer(*(srcBuf.m_buf),
                             *(dstBuf.m_buf),
                             copyRegion
                             );
    return true;
}

vk::DescriptorBufferInfo RawBuffer::getDescBufInfo(size_t offset, size_t end)
{
    vk::DescriptorBufferInfo info;
    size_t size;// = m_expectBytesSize;
    if(offset >= m_expectBytesSize){
        offset = m_expectBytesSize - 1;
    }

    if(end == 0){
        size = m_expectBytesSize - offset;
    }else if(end > offset){
        size = end - offset;
    }else{
        size = 1;
    }

    if(size > m_expectBytesSize - offset){
        size = m_expectBytesSize - offset;
    }

    info.buffer = *m_buf;
    info.offset = offset;
    info.range = size;

    return info;
}

vk::DescriptorSetLayoutBinding
RawBuffer::getDescriptorSetLayoutBinding()
{
    vk::DescriptorSetLayoutBinding ret(
                m_descBinding,
                m_descType,
                m_descBindingArrayLen,
                m_descStage
                );
    return ret;
}

std::shared_ptr<vk::DescriptorBufferInfo> RawBuffer::getDescriptorBufferInfo()
{
    if(!m_buf){
        return nullptr;
    }

    auto info = std::make_shared<vk::DescriptorBufferInfo>(
                *m_buf,
                0, // offset偏移
                m_expectBytesSize
                );
    return info;
}

std::shared_ptr<vk::BufferView> RawBuffer::getBufferView()
{
    {
        using UsageE = vk::BufferUsageFlagBits;
        if(!(m_usage & UsageE::eStorageTexelBuffer)
             && !(m_usage & UsageE::eUniformTexelBuffer)
             ){
            return nullptr;
        }
    }
    return m_bufview;
}



